-- su.2021-8-12.处理jwt，并根据redis key读取用户信息写入Header中

local http = require "socket.http"
local ltn12 = require "ltn12"
local cjson = require "cjson"
local jwt_decoder = require "kong.plugins.my-jwt.jwt_parser"
local req_set_header = ngx.req.set_header
local BasePlugin = require "kong.plugins.base_plugin"

local kong = kong

local TokenAuthHandler = BasePlugin:extend()

TokenAuthHandler.PRIORITY = 1000
TokenAuthHandler.VERSION="0.1.0"

local KEY_PREFIX = "whispir_auth_token"
local EXPIRES_ERR = "token expires"


-- 错误码
local CODE_SUCCESS = 1;     -- 成功
local CODE_FAIL = 0;        -- 失败
local CODE_REDIS_NO_INFO = 2; -- redis没登录信息，前端判断2重新登录

-- 通用返回格式
local function getRetFormat(code_val, msg_val)
  return {code = code_val, msg = msg_val, data = {}, total = 0}
end

-- 获取header中的token
local function extract_token(request)
  local auth_header = request.get_headers()["authorization"]
  if auth_header then
    local iterator, ierr = ngx.re.gmatch(auth_header, "\\s*[Bb]earer\\s+(.+)")
    if not iterator then
      return nil, ierr
    end
    
    local m, err = iterator()
    if err then
      return nil, err
    end
    
    if m and #m > 0 then
      return m[1]
    end
  end
end


-- 返回redis对象
local function get_redis(conf)
  local redis = require "resty.redis";
  local red = redis:new();
 
  red:set_timeout(redis_connection_timeout);-- Time out
 
  local ok, err = red:connect(conf.redis_addr, conf.redis_port);
  ok, err = red:auth(conf.redis_pwd)-- password    
  if not ok then    
    return kong.response.exit(200, getRetFormat(CODE_FAIL, "连接redis失败！" .. tostring(err)))
  else
    red:select(conf.redis_db)
    return red
  end
end


-- 解析token并读取redis，将redis信息写入header中
local function query_and_validate_token(token, conf)
  local request_uri = ngx.var.request_uri
  if false then
    return kong.response.exit(200, getRetFormat(CODE_FAIL, tostring(request_uri)))
  end

  -- 解析token
  local jwt, err = jwt_decoder:new(token)
  if err then
    return kong.response.exit(200, getRetFormat(CODE_FAIL, "解析token错误！" .. tostring(err)))
  end

  local claims = jwt.claims

  -- for k, v in pairs(claims) do
  --   print("claims:data: " .. tostring(k).."="..tostring(v))
  -- end

  -- 读取redis
  local redisKey = conf.redis_key_prefix .. claims[conf.redis_key_name]
  local redis = get_redis(conf)

  local blackValue, err = redis:get(redisKey);
  if err then
    return kong.response.exit(200, getRetFormat(CODE_REDIS_NO_INFO, "信息错误，请重新登录"))
  end

  -- 单点登录，通过判断最后登录时间和token中的最后登录时间是否一致
  if conf.sso == true then
    if tonumber(data.last_login_time) ~= tonumber(claims.last_login_time) then
      return kong.response.exit(200, getRetFormat(CODE_REDIS_NO_INFO, "账号已在其他设备登录，请重新登录！"))
    end
  end

  -- 将用户信息写入header中
  local data = cjson.decode(blackValue)
  for k, v in pairs(data) do
    if type(v) == 'number' then
      req_set_header(tostring(k), tonumber(v))
    else 
      req_set_header(tostring(k), tostring(v))
    end
  end

end

function TokenAuthHandler:new()
  TokenAuthHandler.super.new(self, "my-jwt")
end

function TokenAuthHandler:access(conf)
  TokenAuthHandler.super.access(self)
  
  local token, err = extract_token(ngx.req)
  -- print("access:token:" .. token)
  if err then
    -- ngx.log(ngx.ERR, "failed to extract token: ", err)
    return kong.response.exit(200, getRetFormat(CODE_FAIL, "未登录！"))
  end
  -- ngx.log(ngx.DEBUG, "extracted token: ", token)
  
  local ttype = type(token)
  if ttype ~= "string" then
    if ttype == "nil" then
      return kong.response.exit(200, getRetFormat(CODE_FAIL, "Missing token"))
    end
    if ttype == "table" then
      return kong.response.exit(200, getRetFormat(CODE_FAIL, "Multiple tokens"))
    end
    return kong.response.exit(200, getRetFormat(CODE_FAIL, "Unrecognized token"))
  end
  
  local info
  info, err =  query_and_validate_token(token, conf)
  
  if err then
    print("ngx.ERR:"..ngx.ERR)
    print("failed to validate token:" .. table.concat(err))
    -- ngx.log(ngx.ERR, "failed to validate token: ", err)
    if EXPIRES_ERR == err then
      return kong.response.exit(200, getRetFormat(CODE_FAIL, EXPIRES_ERR))
    end
    return kong.response.exit(200, getRetFormat(CODE_FAIL, err))
  end
  
end

return TokenAuthHandler
